using System;
using System.Collections.Generic;
using System.Linq;
using EventStore;
using EventStore.Dispatcher;
using Sponsorship.Domain;
using Sponsorship.ReadModel;
using StructureMap;

namespace Sponsorship.Infrastructure
{
    /// <summary>
    /// An implementation of <see cref="IBus"/> that uses StructureMap's <see cref="ObjectFactory"/> to find a single handler for a command and 
    /// dispatches the command to that handler.
    /// An implementation of <see cref="IDispatchCommits"/> that routes the events contained in the <see cref="Commit"/> dispatched to it 
    /// to a set of <see cref="IEventHandler{T}"/> instances registered in a <see cref="Dictionary{TKey,TValue}"/>.
    /// </summary>
    // TODO: This class seems to violate the Single Responsibility Principle - split into an InProcessBus and an InProcessEventRouter?
    public class InProcessBus : IBus, IDispatchCommits
    {
        private readonly Dictionary<Type, List<Action<IEvent>>> _routes = new Dictionary<Type, List<Action<IEvent>>>();

        public void Dispatch (Commit commit)
        {
            for (int i = 0; i < commit.Events.Count; i++)
            {
                EventMessage eventMessage = commit.Events[i];
                var @event = eventMessage.Body as IEvent;

                List<Action<IEvent>> handlers;
                if (!_routes.TryGetValue(@event.GetType(), out handlers)) return;

                //AppendHeaders(@event, commit.Headers);
                //AppendHeaders(@event, eventMessage.Headers);
                AppendVersion(commit, i);

                foreach (var handler in handlers)
                    handler(@event);
            }
        }

        public void Dispose()
        {
        }

        /// <summary>
        ///   Register an event handler for an event
        /// </summary>
        public void RegisterHandler<T> (Action<T> handler) where T : IEvent
        {
            List<Action<IEvent>> handlers;
            if (!_routes.TryGetValue(typeof (T), out handlers))
            {
                handlers = new List<Action<IEvent>>();
                _routes.Add(typeof (T), handlers);
            }
            handlers.Add (x => handler ((T) x));
        }

        public void Send<T> (T command) where T : ICommand
        {
            //var transactionHandler = new TransactionHandler();
            //transactionHandler.Execute(command, GetCommandHandlerForCommand<T>());

            //TODO: CommandHandler Chain

            GetCommandHandlerForCommand<T>().Handle(command);
        }

        private static void AppendHeaders (IEvent message, IEnumerable<KeyValuePair<string, object>> headers)
        {
            headers = headers.Where(x => x.Key.StartsWith(BusPrefixKey));
            foreach (var header in headers)
            {
                string key = header.Key.Substring(BusPrefixKey.Length);
                string value = (header.Value ?? string.Empty).ToString();
                message.SetHeader(key, value);
            }
        }

        private static void AppendVersion (Commit commit, int index)
        {
            var busMessage = commit.Events[index].Body as IEvent;
            busMessage.SetHeader(AggregateIdKey, commit.StreamId.ToString());
            busMessage.SetHeader(CommitVersionKey, commit.StreamRevision.ToString());
            busMessage.SetHeader(Event.EventVersionKey, GetSpecificEventVersion(commit, index).ToString());
        }

        private ICommandHandler<T> GetCommandHandlerForCommand<T>() where T : ICommand
        {
            return ObjectFactory.GetInstance<ICommandHandler<T>>();
        }

        private static int GetSpecificEventVersion (Commit commit, int index)
        {
            // e.g. (StreamRevision: 120) - (5 events) + 1 + (index @ 4: the last index) = event version: 120
            return commit.StreamRevision - commit.Events.Count + 1 + index;
        }

        private const string AggregateIdKey = "AggregateId";
        private const string CommitVersionKey = "CommitVersion";
        private const string BusPrefixKey = "Bus.";
    }
}